#ifndef CPP_PROCESS_FILE_HANDLE_HPP
#define CPP_PROCESS_FILE_HANDLE_HPP

#include "config.hpp"
#include "error.hpp"
#include "util.hpp"

#if defined(CPP_POSIX_API)
	#include <sys/types.h>
	#include <cerrno>
	#include <unistd.h>
#elif defined(CPP_WINDOWS_API)
	#include <windows.h>
#else
	#error "Unsupported platform."
#endif

#include <cassert>
#include <istream>
#include <ostream> 
#include <streambuf>
#include <cstddef>

NAMESPACE_STD
NAMESPACE_PROCESS

class FileHandle;
class ProcessIstream;
class ProcessOstream;
class SystemBuf;

class FileHandle
{
public:
#if defined(CPP_POSIX_API)
    typedef int Handle;
#elif defined(CPP_WINDOWS_API)
    typedef HANDLE Handle;
#endif

    FileHandle()
        : handle_(invalid_value())
    {
    }

    FileHandle(Handle h) : handle_(h)
    {
        assert(handle_ != invalid_value());
    }

    FileHandle(const FileHandle &fh)
        : handle_(fh.handle_)
    {
        fh.handle_ = invalid_value();
    }

    ~FileHandle()
    {
        if (valid())
            close();
    }

    FileHandle &operator=(const FileHandle &fh)
    {
        handle_ = fh.handle_;
        fh.handle_ = invalid_value();
        return *this;
    }

    bool valid() const
    {
        return handle_ != invalid_value();
    }

    void close()
    {
        assert(valid());

#if defined(CPP_POSIX_API)
        ::close(handle_);
#elif defined(CPP_WINDOWS_API)
        ::CloseHandle(handle_);
#endif

        handle_ = invalid_value();
    }

    Handle release()
    {
        assert(valid());

        Handle h = handle_;
        handle_ = invalid_value();
        return h;
    }

    Handle get() const
    {
        assert(valid());

        return handle_;
    }

#if defined(CPP_POSIX_API)

    void posix_remap(Handle h)
    {
        assert(valid());

        if (::dup2(handle_, h) == -1)
            throw system_error(errno, "file_handle::posix_remap: dup2(2) failed");

        if (::close(handle_) == -1)
        {
            ::close(h);
            throw system_error(errno, "file_handle::posix_remap: close(2) failed");
        }

        handle_ = h;
    }

    static FileHandle posix_dup(int h1, int h2)
    {
        if (::dup2(h1, h2) == -1)
            throw system_error(errno, "file_handle::posix_dup: dup2(2) failed");

        return FileHandle(h2);
    }
#endif

#if defined(CPP_WINDOWS_API)

    static FileHandle win32_dup(HANDLE h, bool inheritable)
    {
        HANDLE h2;
        if (!::DuplicateHandle(::GetCurrentProcess(), h, ::GetCurrentProcess(), &h2, 0, inheritable ? TRUE : FALSE, DUPLICATE_SAME_ACCESS))
            throw SystemError(::GetLastError(), "file_handle::win32_dup: DuplicateHandle failed");

        return FileHandle(h2);
    }

    static FileHandle win32_std(DWORD d, bool inheritable)
    {
        assert(d == STD_INPUT_HANDLE || d == STD_OUTPUT_HANDLE || d == STD_ERROR_HANDLE);

        HANDLE h = ::GetStdHandle(d);
        if (h == INVALID_HANDLE_VALUE)
            throw SystemError(::GetLastError(), "file_handle::win32_std: GetStdHandle failed");

        return win32_dup(h, inheritable);
    }

    void win32_set_inheritable(bool i)
    {
        assert(valid());

        if (!::SetHandleInformation(handle_, HANDLE_FLAG_INHERIT, i ? HANDLE_FLAG_INHERIT : 0))
            throw SystemError(::GetLastError(), "file_handle::win32_set_inheritable: SetHandleInformation failed");
    }
#endif

private:
    mutable Handle handle_;

    static const Handle invalid_value()
    {
#if defined(CPP_POSIX_API)
        return -1;
#elif defined(CPP_WINDOWS_API)
        return INVALID_HANDLE_VALUE;
#endif
	}
};

class SystemBuf : public std::streambuf, public NonCopyable
{
    friend class ProcessOstream;

public:
#if defined(CPP_POSIX_API)
    typedef int Handle;
#elif defined(CPP_WINDOWS_API)
    typedef HANDLE Handle;
#endif

    explicit SystemBuf(Handle h, std::size_t bufsize = 8192)
        : handle_(h),
        buf_size_(bufsize),
        read_buf_(new char[bufsize]),
        write_buf_(new char[bufsize])
    {
#if defined(CPP_POSIX_API)
        assert(handle_ >= 0);
#elif defined(CPP_WINDOWS_API)
        assert(handle_ != INVALID_HANDLE_VALUE);
#endif
        assert(buf_size_ > 0);

        setp(write_buf_, write_buf_ + buf_size_);
    }

    ~SystemBuf()
    {
        if (read_buf_)
        {
            delete read_buf_;
            read_buf_ = NULL;
        }
        if (write_buf_)
        {
            delete write_buf_;
            write_buf_ = NULL;
        }
    }

protected:
    /**
     * Reads new data from the native file handle.
     *
     * This operation is called by input methods when there is no more
     * data in the input buffer. The function fills the buffer with new
     * data, if available.
     *
     * \pre All input positions are exhausted (gptr() >= egptr()).
     * \post The input buffer has new data, if available.
     * \returns traits_type::eof() if a read error occurrs or there are
     *          no more data to be read. Otherwise returns
     *          traits_type::to_int_type(*gptr()).
     */
    virtual int_type underflow()
    {
        assert(gptr() >= egptr());

        bool ok;
#if defined(CPP_POSIX_API)
        ssize_t cnt = ::read(handle_, read_buf_, buf_size_);
        ok = (cnt != -1 && cnt != 0);
#elif defined(CPP_WINDOWS_API)
        DWORD cnt;
        BOOL res = ::ReadFile(handle_, read_buf_, buf_size_, &cnt, NULL);
        ok = (res && cnt > 0);
#endif

        if (!ok)
            return traits_type::eof();
        else
        {
            setg(read_buf_, read_buf_, read_buf_ + cnt);
            return traits_type::to_int_type(*gptr());
        }
    }

    /**
     * Makes room in the write buffer for additional data.
     *
     * This operation is called by output methods when there is no more
     * space in the output buffer to hold a new element. The function
     * first flushes the buffer's contents to disk and then clears it to
     * leave room for more characters. The given \a c character is
     * stored at the beginning of the new space.
     *
     * \pre All output positions are exhausted (pptr() >= epptr()).
     * \post The output buffer has more space if no errors occurred
     *       during the write to disk.
     * \post *(pptr() - 1) is \a c.
     * \returns traits_type::eof() if a write error occurrs. Otherwise
     *          returns traits_type::not_eof(c).
     */
    virtual int_type overflow(int c)
    {
        assert(pptr() >= epptr());

        if (sync() == -1)
            return traits_type::eof();

        if (!traits_type::eq_int_type(c, traits_type::eof()))
        {
            traits_type::assign(*pptr(), c);
            pbump(1);
        }

        return traits_type::not_eof(c);
    }

    /**
     * Flushes the output buffer to disk.
     *
     * Synchronizes the systembuf buffers with the contents of the file
     * associated to this object through the native file handle. The
     * output buffer is flushed to disk and cleared to leave new room
     * for more data.
     *
     * \returns 0 on success, -1 if an error occurred.
     */
    virtual int sync()
    {
#if defined(CPP_POSIX_API)
        ssize_t cnt = pptr() - pbase();
#elif defined(CPP_WINDOWS_API)
        long cnt = pptr() - pbase();
#endif

        bool ok;
#if defined(CPP_POSIX_API)
        ok = ::write(handle_, pbase(), cnt) == cnt;
#elif defined(CPP_WINDOWS_API)
        DWORD rcnt;
        BOOL res = ::WriteFile(handle_, pbase(), cnt, &rcnt, NULL);
        ok = (res && static_cast<long>(rcnt) == cnt);
#endif

        if (ok)
            pbump(-cnt);
        return ok ? 0 : -1;
    }

private:
    /**
     * Native file handle used by the systembuf object.
     */
    Handle handle_;

    /**
     * Internal buffer size used during read and write operations.
     */
    std::size_t buf_size_;

    /**
     * Internal buffer used during read operations.
     */
    char* read_buf_;

    /**
     * Internal buffer used during write operations.
     */
    char* write_buf_;
};

class ProcessIstream : public std::istream, public NonCopyable
{
public:
    /**
     * Creates a new process' output stream.
     *
     * Given a file handle, this constructor creates a new pistream
     * object that owns the given file handle \a fh. Ownership of
     * \a fh is transferred to the created pistream object.
     *
     * \pre \a fh is valid.
     * \post \a fh is invalid.
     * \post The new pistream object owns \a fh.
     */
    explicit ProcessIstream(FileHandle &fh)
        : std::istream(0),
        file_handle_(fh),
        system_buf_(file_handle_.get())
    {
        rdbuf(&system_buf_);
    }

    /**
     * Returns the file handle managed by this stream.
     *
     * The file handle must not be copied. Copying invalidates
     * the source file handle making the pistream unusable.
     */
    FileHandle &file_handle()
    {
        return file_handle_;
    }

    /**
     * Closes the file handle managed by this stream.
     *
     * Explicitly closes the file handle managed by this stream. This
     * function can be used by the user to tell the child process it's
     * not willing to receive more data.
     */
    void Close()
    {
        file_handle_.close();
    }

private:
    /**
     * The file handle managed by this stream.
     */
    FileHandle file_handle_;

    /**
     * The systembuf object used to manage this stream's data.
     */
    SystemBuf system_buf_;
};

class ProcessOstream : public std::ostream, public NonCopyable
{
public:
    /**
     * Creates a new process' input stream.
     *
     * Given a file handle, this constructor creates a new postream
     * object that owns the given file handle \a fh. Ownership of
     * \a fh is transferred to the created postream object.
     *
     * \pre \a fh is valid.
     * \post \a fh is invalid.
     * \post The new postream object owns \a fh.
     */
    explicit ProcessOstream(FileHandle &fh)
        : std::ostream(0),
        file_handle_(fh),
        system_buf_(file_handle_.get())
    {
        rdbuf(&system_buf_);
    }

    /**
     * Returns the file handle managed by this stream.
     *
     * The file handle must not be copied. Copying invalidates
     * the source file handle making the postream unusable.
     */
    FileHandle &file_handle()
    {
        return file_handle_;
    }

    /**
     * Closes the file handle managed by this stream.
     *
     * Explicitly closes the file handle managed by this stream. This
     * function can be used by the user to tell the child process there
     * is no more data to send.
     */
    void Close()
    {
        system_buf_.sync();
        file_handle_.close();
    }

private:
    /**
     * The file handle managed by this stream.
     */
    FileHandle file_handle_;

    /**
     * The systembuf object used to manage this stream's data.
     */
    SystemBuf system_buf_;
};


END_NAMESPACE
END_NAMESPACE

#endif
